{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# SOLT Calibration Standards Creation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Introduction\n",
    "\n",
    "In scikit-rf, a calibration standard is treated just as a regular one-port or\n",
    "two-port `skrf.Network`, defined by its full S-parameters. It can represent\n",
    "reflection, transmission, load, and even arbitrary-impedance standards. Since\n",
    "no additional errors are introduced in circuit modeling and fitting, this\n",
    "approach allows the highest calibration accuracy, and is known as a\n",
    "*data-based standard* in the terminology of VNA vendors.\n",
    "\n",
    "However, VNA calibration standards are traditionally defined using a circuit\n",
    "model with fitted coefficients. Since data-based standards are often offered\n",
    "as premium products, many model-based calibration kits are still\n",
    "being used and manufactured (even at 50 GHz [[1](SOLT%20Calibration%20Standards%20Creation.html#ref1)]).\n",
    "This necessitates the creation of their network models before they can be used\n",
    "in scikit-rf's calibration routines.\n",
    "\n",
    "This example explains network creation from coefficients given in both the\n",
    "HP-Agilent-Keysight format and the Rohde & Schwarz / Anritsu format. Both\n",
    "are essentially the same circuit model, but the latter format uses different\n",
    "units of measurement for an offset transmission line.\n",
    "\n",
    "Calculated responses for Keysight 85032F (Type-N plug), Keysight 85033E\n",
    "(3.5 mm plug), generic flush standards (SMA plugs), and Maury Microwave\n",
    "8050CK10 (3.5 mm plug) are also plotted in this document as examples.\n",
    "\n",
    ".. note::\n",
    "Only coaxial standards are covered by this guide. The calculation is different\n",
    "for waveguides. In particular, the scaling by $\\sqrt{\\text{GHz}}$ for coaxial\n",
    "lines cannot be applied to waveguides because loss is also a function of their\n",
    "physical dimensions, with significantly more complicated formulas. Do you have\n",
    "waveguide experience? If so, you can help by\n",
    "[contributing](../../contributing/index.rst#examples-and-tutorials) to the doc.\n",
    "\n",
    "## Alternatives to scikit-rf Modeling\n",
    "\n",
    "Before we begin, it's worth pointing out some alternatives.\n",
    "\n",
    "In scikit-rf, you are able to use any existing standard definition by\n",
    "its S-parameters. If you already have your standard defined as a network\n",
    "in other tools (e.g. in your favorite circuit simulator, or actual\n",
    "measurements results), you can simply export the S-parameters to Touchstone\n",
    "files for use in scikit-rf. Similarly, if you're already using a data-based\n",
    "calibration standard, it should be possible to use its data directly. The\n",
    "S-parameters may be stored in device-specific file formats, consult your\n",
    "vendor about whether they can be exported as a Touchstone file.\n",
    "\n",
    "As a special case, if *flush* (zero-length) standards are used - meaning that the\n",
    "standards sit right at the connector's reference plane without extended bodies -\n",
    "sometimes one can assume the calibration standards are ideal for non-critical\n",
    "measurements. In scikit-rf, one can create ideal responses conveniently by defining\n",
    "an ideal transmission line and calling the `short()`, `open()`, `match()`,\n",
    "and `thru()` methods (explained in the [Preparation](#preparation) section).\n",
    "\n",
    ".. important::\n",
    "    Ideal assumptions are only approximately valid for *flush* standards.\n",
    "    Most lab-grade standards are *offset* standards, always use these standards\n",
    "    with the correct definitions entered. They have additional\n",
    "    electrical delays from their extended bodies, phase errors would be\n",
    "    unacceptably large under ideal assumptions. Using a *flush* standard behind\n",
    "    a *Thru* adapter creates the same phase deviations. See below for the\n",
    "    distinction between *flush* and *offset* standards. \n",
    "\n",
    "## HP-Agilent-Keysight Coefficient Format\n",
    "\n",
    "After the necessary background is introduced, let's begin.\n",
    "\n",
    "For the purpose of this guide, we're going to model the Keysight 85032F,\n",
    "Type-N, 50 Ω, DC to 9 GHz calibration kit (plug), with the following\n",
    "coefficients.\n",
    "\n",
    "|    Parameter    |            Unit             |    Open   |   Short   |  Load   |   Thru   |\n",
    "| --------------- | --------------------------- | --------- | --------- | ------- | -------- |\n",
    "| $\\text{C}_0$    |  $10^{-15} \\text{ F}$       |   89.939  |           |         |          |\n",
    "| $\\text{C}_1$    |  $10^{-27} \\text{ F/Hz}$    | 2536.800  |           |         |          |\n",
    "| $\\text{C}_2$    |  $10^{-36} \\text{ F/Hz}^2$  | -264.990  |           |         |          |\n",
    "| $\\text{C}_3$    |  $10^{-45} \\text{ F/Hz}^3$  |   13.400  |           |         |          |\n",
    "| $\\text{L}_0$    |  $10^{-12} \\text{ H}$       |           | 3.3998    |         |          |\n",
    "| $\\text{L}_1$    |  $10^{-24} \\text{ H/Hz}$    |           | -496.4808 |         |          |\n",
    "| $\\text{L}_2$    |  $10^{-33} \\text{ H/Hz}^2$  |           |  34.8314  |         |          |\n",
    "| $\\text{L}_3$    |  $10^{-42} \\text{ H/Hz}^3$  |           |  -0.7847  |         |          |\n",
    "|  Resistance     |         $\\Omega$            |           |           |    50   |          |\n",
    "| Offset Delay    |            ps               |  40.856   |   45.955  |     0   |     0    |\n",
    "| Offset Loss     | $\\text{G}\\Omega$ / s        |   0.93    |    1.087  |     0   |     0    |\n",
    "| Offset $Z_0$    |         $\\Omega$            |    50     |   49.992  |    50   |    50    |\n",
    "| Reference $Z_0$ |         $\\Omega$            |    50     |     50    |    50   |    50    |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Circuit Model\n",
    "\n",
    "Before we start creating their network definitions, we first need to know\n",
    "the underlying circuit model and the meaning of these coefficients.\n",
    "As this schematic shows, this is the HP-Agilent-Keysight model for\n",
    "a calibration standard.\n",
    "\n",
    "<figure>\n",
    "<img src=\"solt_calibration_standards_creation/calkit-schematic.svg\" width=\"40%\">\n",
    "<figcaption>*Circuit Model of Calibration Standards*</figcaption>\n",
    "</figure>\n",
    "\n",
    "#### Termination Impedance\n",
    "\n",
    "A shunt impedance is connected at the end of an *offset*\n",
    "transmission line, and models the distributed capacitance or\n",
    "inductance in the open or short standard. It's given as a third-degree\n",
    "polynomial with four coefficients, $y(f) = a_0 + a_1 f + a_2 f^2 + a_3 f^3$, where $f$ is the frequency and $a_i$ are the coefficients.\n",
    "For an open standard, they're $\\text{C}_0$, $\\text{C}_1$, $\\text{C}_2$,\n",
    "$\\text{C}_3$, the first constant term is in femtofarad. For a short\n",
    "standard, they're $\\text{L}_0$, $\\text{L}_1$, $\\text{L}_2$, $\\text{L}_3$,\n",
    "the first constant term is in picohenry.\n",
    "\n",
    ".. important::\n",
    "  The reflection coefficient ($S_{11}$) of the termination impedance is\n",
    "  calculated with respect to the system impedance ($Z_\\mathrm{ref}$), not\n",
    "  the impedance of the offset transmission line ($Z_\\text{off}$ or $Z_\\text{c}$).\n",
    "  [[15](SOLT%20Calibration%20Standards%20Creation.html#ref15)]\n",
    "\n",
    "#### Offset Line\n",
    "\n",
    "In front of the calibration standard, there exists an *offset* lossy\n",
    "transmission line. To understand its context, we need to distinguish\n",
    "two types of calibration standards: *flush* (zero-length) and *offset*\n",
    "standards.\n",
    "\n",
    "In a *flush* standard, the physical standard is located directly at the\n",
    "reference plane of the port. This design is widely used in uncharacterized\n",
    "generic SMA calibration \"standards\" (if they can be called as such) in\n",
    "amateur radio. *Flush* standards are not always of low quality, they're\n",
    "also found in some lab-grade standards, such as Keysight 85031B for\n",
    "APC-7 connectors."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "<figure>\n",
    "<img src=\"solt_calibration_standards_creation/flush-calkit.jpg\" width=\"45%\">\n",
    "<img src=\"solt_calibration_standards_creation/offset-calkit.jpg\" width=\"45%\">\n",
    "<figcaption>*Flush standards sit flush against the\n",
    "connector, with no additional structure. Offset standards have extended bodies,\n",
    "introducing electrical delays.*</figcaption>\n",
    "</figure>\n",
    "\n",
    "However, most lab-grade standards are designed as *offset* standards, with a\n",
    "short length between the reference plane of the connector and the physical\n",
    "location of the standard. This can be done to improve the predictability of a standard's\n",
    "electrical characteristics [[2](SOLT%20Calibration%20Standards%20Creation.html#ref2)],\n",
    "or merely a limitation of a connector's mechanical design (e.g. all Type-N connectors\n",
    "have a reference plane offset of 5.258-5.360 mm\n",
    "[[3](SOLT%20Calibration%20Standards%20Creation.html#ref3)]). Both factors introduce an\n",
    "electrical length that must be accounted for, otherwise it would introduce a large phase\n",
    "deviation.\n",
    "\n",
    "This *offset* length is modeled as a transmission line using three parameters:\n",
    "\n",
    "1. **Lossless Offset Impedance** ($Z_\\text{off}$): A real characteristic impedance, with\n",
    "   line loss ($R$ and $G$) neglected.\n",
    "   This often matches the VNA's reference impedance, but not always. Sometimes a\n",
    "   value slightly different from the reference impedance is used to model\n",
    "   physical imperfections, such as 50.209 Ω or 49.992 Ω.\n",
    "   Also, waveguide standards use a special normalized value `1`.\n",
    "\n",
    "2. **Offset Delay** ($t_\\text{delay}$) - One-way electrical delay, given in picoseconds (ps).\n",
    "\n",
    "3. **Offset Loss** ($A_\\text{loss}$). The nominal attenuation at 1 GHz per unit time,\n",
    "   given in GΩ/s (gigaohms per second). Scale this value by\n",
    "   $\\sqrt{f / (\\text{1 GHz})}$ for other frequencies.\n",
    "   The unit GΩ/s is unconventional, but for a reason. To express a transmission line\n",
    "   segment's length, both a distance (in meters) or a time delay (in seconds) can\n",
    "   be used. Defining the line with respect to time allows calculating\n",
    "   $R = A_\\text{loss} t_\\text{delay}$ in ohms, without needing to explicitly\n",
    "   define the medium permittivity $\\epsilon$.\n",
    "\n",
    ".. important::\n",
    "    These parameters are given in datasheets in scaled units, but unscaled SI units\n",
    "    should be use in all calculations (without prefix): ohms (Ω), seconds (s), ohms\n",
    "    per second (Ω/s), and hertz (Hz).\n",
    "\n",
    "##### RLCG parameters\n",
    "\n",
    "<figure>\n",
    "<img src=\"solt_calibration_standards_creation/rlcg.svg\" width=\"40%\">\n",
    "<figcaption>*RLCG transmission line model*</figcaption>\n",
    "</figure>\n",
    "\n",
    "As these parameters are non-standard, we need to convert them to the familiar RLCG\n",
    "line parameters. After algebraic manipulation of\n",
    "[[4](SOLT%20Calibration%20Standards%20Creation.html#ref4)], equation 2B.4,\n",
    "the corresponding frequency-dependent RLCG parameters are computed as follows.\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "R(f) \\cdot l &= \\left(A_\\text{loss} t_\\text{delay}\\right) \\cdot \\sqrt{\\frac{f}{10^9}} \\\\\n",
    "L(f) &= L_0 + L_\\text{cond} = t_\\text{delay}  Z_\\text{off} + \\dfrac{R}{2\\pi f} \\\\\n",
    "C &= \\dfrac{t_\\text{delay}}{Z_\\text{off}} \\\\\n",
    "G &= 0 \\\\\n",
    "l &= 1\\text{ (normalized)}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "The term $L_0$ is the ideal line inductance, $L_\\text{cond} = R / (2 \\pi f)$ is\n",
    "the frequency-dependent inductance of imperfect conductors due to skin effect.\n",
    "In [[5](SOLT%20Calibration%20Standards%20Creation.html#ref5)], nearly the same\n",
    "equations were derived, except that the term $L_\\text{cond}$ has been omitted.\n",
    "It must be included here to be consistent with the original Keysight publication\n",
    "[[4](SOLT%20Calibration%20Standards%20Creation.html#ref4)] (otherwise the phase\n",
    "error would be greater than 0.1 degrees at 9 GHz for the 85032F).\n",
    "\n",
    "Using these parameters, one can calculate the *propagation constant*\n",
    "($\\gamma$) and the complex *characteristic impedance* ($Z_c$) of the lossy line.\n",
    "Note that in the frequency-dependent `RLCG(f)` model, each frequency has its own\n",
    "RLCG parameters and line constants.\n",
    "\n",
    ".. important:: Our formulation is *not* exactly identical to the official\n",
    "Keysight calculations and other works. The latter model used a low-loss\n",
    "approximation when calculating line constants, while our\n",
    "formulation starts from the RLCG parameters to derive the exact line constants.\n",
    "Both formulations have negligible differences (Keysight 85056A's magnitude\n",
    "and phase angle are in agreement to 4 decimal places at 50 GHz), this difference\n",
    "is only academic.\n",
    "\n",
    ".. note::  We show the RLCG approach due to its wide understanding and ease\n",
    "of use. If reproducing the exact numerical values down to the last digit\n",
    "is absolutely required, see *Appendix* for derivation and code for\n",
    "Keysight's official low-loss approximation.\n",
    "\n",
    "#### Reference Impedance\n",
    "\n",
    "All calibration standards have an implied *reference impedance* $Z_\\text{ref}$.\n",
    "Some Keysight datasheets show it as the \"System $Z_0$\"\n",
    "[[6](SOLT%20Calibration%20Standards%20Creation.html#ref6)], but it's not always\n",
    "given out explicitly. One may require some context to deduce it: if a standard\n",
    "is named *Keysight 85032F Type N (50) Calibration Kit*, $Z_\\text{ref}$ is 50 Ω.\n",
    "\n",
    "It's not to be confused with the offset transmission line's characteristic\n",
    "impedance $Z_0$ (lossless) or $Z_c$ (lossy), which can have a value of 49.992 Ω.\n",
    "Unlike the *line impedance*,\n",
    "the *reference impedance's* purpose is to renormalize the S-parameters to the\n",
    "standard form, since all S-parameters are relative. This is always set to the\n",
    "reference impedance of the VNA system: 50 Ω or 75 Ω.\n",
    "\n",
    "##### Connector Discontinuity\n",
    "\n",
    "Since the reference impedance appears to be an arbitrary choice, one may conclude\n",
    "that any calibration standard can be used to calibrate any VNA port (as long as\n",
    "they're mechanically compatible) regardless of its offset line impedance. For\n",
    "example, one may attempt to calibrate a 50 Ω VNA port by connecting it directly\n",
    "to a 75 Ω calibration standard, so that all calibrated measurements behave as if\n",
    "they were made using a 75 Ω system. Likewise, if the reference impedance of a 75 Ω\n",
    "calibration standard is renormalized, one can apparently predict its ideal\n",
    "S-parameters on a 50 Ω VNA in order to abuse it as a 50 Ω standard.\n",
    "\n",
    "However, this assumption is not true. Renormalization cannot predict the parasitic\n",
    "effects at the physical test port due to discontinuity of the electromagnetic field,\n",
    "from a sudden change of the transmission line's physical dimensions.\n",
    "[[7](SOLT%20Calibration%20Standards%20Creation.html#ref7)][[9](SOLT%20Calibration%20Standards%20Creation.html#ref9)]\n",
    "It's approximately a capacitive discontinuity.\n",
    "[[18](SOLT%20Calibration%20Standards%20Creation.html#ref18)]\n",
    "This effect exists even if both sides have the same characteristic impedance, as\n",
    "long as the coaxial line's physical dimensions have a step change (e.g. 3.5 mm to\n",
    "2.92 mm port). [[8](SOLT%20Calibration%20Standards%20Creation.html#ref8)] \n",
    "\n",
    "In theory, the excitation of high-order modes at a discontinuity makes calibration\n",
    "unreliable, since it can be sensitive to perturbation and not repeatable. Indeed,\n",
    "pathological cases have been observed at 50 GHz even between a pair of \"perfect\"\n",
    "1.85 mm connectors - the mated connector pair generates a resonance because its\n",
    "mechanical tolerance is *too precise*, the near-zero gap forms a resonator.\n",
    "[[25](SOLT%20Calibration%20Standards%20Creation.html#ref25)][[26](SOLT%20Calibration%20Standards%20Creation.html#ref26)]\n",
    "\n",
    "Nevertheless, in most practical measurements unaffected by such\n",
    "pathological phenomena, it has been showed that, if the DUT and the calibration\n",
    "standards have the same connector design, both produce a similar discontinuity,\n",
    "this discontinuity can often be calibrated\n",
    "out, leaving only a tiny error negligible for all but the most demanding metrological\n",
    "applications. [[24](SOLT%20Calibration%20Standards%20Creation.html#ref24)] Such is the case when\n",
    "measuring 75 Ω devices after calibration by 75 Ω standards using a 50 Ω port.\n",
    "[[9](SOLT%20Calibration%20Standards%20Creation.html#ref9)] However,\n",
    "if the DUT and the calibration standards have different connector designs (e.g.\n",
    "cross-connecting 3.5 mm, 2.92 mm and SMA), this discontinuity produces a slight\n",
    "measurement error. [[8](SOLT%20Calibration%20Standards%20Creation.html#ref8)]\n",
    "\n",
    "Thus, for satisfactory results, the calibration standards and the DUT must\n",
    "use the same connector. For the best result, the test port (or adapter) must\n",
    "be of high quality and matches the calibration standards as well.\n",
    "\n",
    "#### Fitting Non-Uniqueness and Physical Interpretability\n",
    "\n",
    "The phase shift of a small capacitance or inductance is approximately\n",
    "linear, it's often possible to model it equally well as a termination\n",
    "reactance, or as an electrical delay in an offset transmission line.\n",
    "[[4](SOLT%20Calibration%20Standards%20Creation.html#ref4)][[10](SOLT%20Calibration%20Standards%20Creation.html#ref10)]\n",
    "\n",
    "For example, Keysight 85033D's datasheet show two models for the short\n",
    "standard, either as a pure time delay, or a time delay with inductance.\n",
    "[[6](SOLT%20Calibration%20Standards%20Creation.html#ref6)]\n",
    "\n",
    "Due to the non-uniqueness of standard definitions, caution is required\n",
    "when interpreting individual terms in standard definitions: the overall\n",
    "response is meaningful, the individual terms may or may not. Most lab-grade\n",
    "calibration standards attempt to ensure that the standard definitions are\n",
    "physically meaningful [[11](SOLT%20Calibration%20Standards%20Creation.html#ref11)],\n",
    "but it's not always guaranteed.\n",
    "\n",
    "When defining a custom calibration standard via curve fitting, separating\n",
    "the phase shift due to the offset transmission line and termination reactances\n",
    "is difficult. With prior knowledge, one can fix the offset length by mechanical\n",
    "measurements [[12](SOLT%20Calibration%20Standards%20Creation.html#ref12)], or\n",
    "fixing the termination capacitance by electromagnetic theory or simulation.\n",
    "[[10](SOLT%20Calibration%20Standards%20Creation.html#ref10)]\n",
    "Both require precise knowledge and modeling of\n",
    "the standard's design. For example, historical studies focused on hollow\n",
    "air-filled open standards (using the theory of circular waveguide below\n",
    "cutoff) [[10](SOLT%20Calibration%20Standards%20Creation.html#ref10)], but\n",
    "it's no longer applicable today as many open standards use dielectric support. When there's a lack of\n",
    "information, a delay-only model may be the only practical choice.\n",
    "\n",
    ".. tip::\n",
    "   In fact, a delay-only short standard is the only model supported by older VNA's\n",
    "   firmware, such as the HP 8510 [[13](SOLT%20Calibration%20Standards%20Creation.html#ref13)].\n",
    "   External calibration via scikit-rf allows you to bypass this limitation.\n",
    "\n",
    "#### Neglected Terms\n",
    "\n",
    "Both an open or short circuit can be modeled as an electrical delay only\n",
    "without reactance, or vice versa. The delay-only model is most common for\n",
    "the short standard, since it was used historically.\n",
    "\n",
    "A matched load generates little reflection, thus it's often simply modeled as\n",
    "a $Z_0$ termination. Since its reflection coefficient is approximately 0, its\n",
    "phase angle is meaningless, its electrical delay is thus also neglected.\n",
    "\n",
    "The Thru standard is sometimes modeled with an offset delay only, if loss is\n",
    "considered negligible.\n",
    "\n",
    "Some calibration kits don't have any Thru delay data. These kits are meant for\n",
    "*Flush Thru*, *Swap Equal Adapters*, or *Unknown Thru* calibration, without using\n",
    "*Thru*'s data.\n",
    "\n",
    "*Flush Thru* is\n",
    "the traditional SOLT calibration practice: port 1 and port 2 are connected directly,\n",
    "without using adapters. This *Thru* is ideal by definition and has zero length, no\n",
    "modeling is required. In a *Swap Equal Adapters* calibration, a pair of two\n",
    "delay-matched adatpers are involved, making the *Thru*'s actual length invisible. The\n",
    "*Unknown Thru* calibration algorithm is relatively newer, which doesn't require a\n",
    "characterized Thru.\n",
    "\n",
    ".. note::\n",
    "   Sometimes an offset loss still appears in the Load and Thru's definitions, but\n",
    "   with a zero electrical length. These parameters should be ignored. The\n",
    "   zero offset delay disables the offset transmission line.\n",
    "\n",
    "#### Plug and Socket\n",
    "\n",
    "Finally, it's worth clarifying Keysight's plug and socket notations. If a *plug*\n",
    "standard is used to calibrate a *socket*, it's denoted by datasheets as `-m-`\n",
    "with respect to itself, or `(f)` with respect to the test port. If a *socket*\n",
    "standard is used to calibrate a plug, it's denoted by datasheets as `-f-` with\n",
    "respect to itself, or `(m)` with respect to the test port.\n",
    "[[14](SOLT%20Calibration%20Standards%20Creation.html#ref14)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Preparation\n",
    "\n",
    "<div id=\"preparation\"></div>\n",
    "\n",
    "Equipped with this circuit model, we can start to model the calibration\n",
    "standards.\n",
    "\n",
    "First, we need to import some library definitions, specify the frequency range of our\n",
    "calculation. Here, we used 1 MHz to 9 GHz, with 1001 points. You may want to adjust it\n",
    "for your needs. We also define an `ideal_medium` with a $50 \\space\\Omega$ port\n",
    "impedance for the purpose of some future calculations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import skrf\n",
    "from skrf.media import DefinedGammaZ0, DistributedCircuit\n",
    "\n",
    "# reference impedance of the S-parameters (not the offset line)\n",
    "z0_ref = 50\n",
    "freq = skrf.Frequency(1, 9000, 1001, \"MHz\")\n",
    "ideal_medium = DefinedGammaZ0(frequency=freq, z0=z0_ref)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ideal Responses\n",
    "\n",
    "It's useful to know the special case first: ideal calibration standards are easily\n",
    "created by calling the `open()`, `short()`, `match()`, and `thru()` methods in the\n",
    "`ideal_medium`, the first three return a 1-port network. The `thru()` method returns\n",
    "a two-port network."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ideal_open  = ideal_medium.open()\n",
    "ideal_short = ideal_medium.short()\n",
    "ideal_load  = ideal_medium.match()\n",
    "ideal_thru  = ideal_medium.thru()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Modeling the Offset Transmission Line\n",
    "\n",
    "To correctly model the offset transmission line, one should use the\n",
    "offset delay, offset loss, and offset $Z_0$ to derive the RLCG parameters\n",
    "of the lossy line. Internally, `scikit-rf` automatically derives its\n",
    "*propagation constant* ($\\gamma$) and the complex *characteristic impedance*\n",
    "($Z_c$) of the lossy line.\n",
    "\n",
    "The relationship between the offset line parameters and RLCG line\n",
    "parameters has already been given in the discussion above.  Let's\n",
    "translate these formulas to code.\n",
    "\n",
    ".. important:\n",
    "   The term $\\sqrt{\\frac{f}{10^9}}$ scales the line loss from the nominal\n",
    "   1 GHz value to a given frequency, but this is only valid for coaxial lines."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def offset_rlcg(freq, offset_delay, offset_loss, offset_z0):\n",
    "    r = offset_loss * offset_delay * np.sqrt(freq.f / 1e9)\n",
    "    l = (offset_delay * offset_z0) + r / (2 * np.pi * freq.f)\n",
    "    c = offset_delay / offset_z0\n",
    "    g = 0\n",
    "    return (r, l, c, g)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ".. tip::\n",
    "The broadcasting feature in `numpy` is used here. The quantities\n",
    "`r`, `l` are frequency-dependent, thus they're arrays, not scalars.\n",
    "But instead of looping over each frequency explicitly and adding\n",
    "them to an array, here, arrays are automatically created by the\n",
    "multiplication of a scalar and a `numpy.array`. We'll continue to\n",
    "use this technique.\n",
    "\n",
    "With the function `offset_rlcg()` defined, we can now calculate the\n",
    "line constants for the open and short standards by calling it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rlcg_open = offset_rlcg(freq, 40.856e-12, 0.93e9, 50)\n",
    "rlcg_short = offset_rlcg(freq, 45.955e-12, 1.087e9, 49.992)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At this point, we already have everything we need to know about this offset line.\n",
    "The other half of the task is straightforward: create a two-port network for\n",
    "this transmission line in scikit-rf using these RLCG parameters, scikit-rf will\n",
    "help us automatically calculating its propagation constant $\\gamma l$ and the complex\n",
    "characteristic impedance $Z_c$.\n",
    "\n",
    "It's easy to perform this task in scikit-rf:\n",
    "\n",
    "1. First, create a `DistributedCircuit` medium with four arrays (RLCG) as inputs.\n",
    "The created `DistributedCircuit` represents a physical medium.\n",
    "\n",
    "2. When creating the medium, we also need to specify the S-parameter reference\n",
    "   impedance $Z_\\text{ref}$ as `z0_port=50` (or `z0_port=75`).\n",
    "\n",
    "   * This reference impedance $Z_\\text{ref}$ is not to be confused with the offset\n",
    "     line impedances $Z_\\text{off}$ and $Z_c$. $Z_\\text{ref}$ (real) is always the nominal\n",
    "     system or port impedance of the VNA, while the line impedances $Z_\\text{off}$ (real)\n",
    "     and $Z_c$ (complex) may differ due to physical imperfection and losses. \n",
    "\n",
    "3. Then, an actual line with a 1-meter length is derived by calling the medium's `line()`\n",
    "   method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "medium_open = DistributedCircuit(frequency=freq,\n",
    "    R=rlcg_open[0], L=rlcg_open[1], C=rlcg_open[2], G=rlcg_open[3],\n",
    "    z0_port=z0_ref\n",
    ")\n",
    "line_open = medium_open.line(\n",
    "    d=1, unit='m'\n",
    ")\n",
    "\n",
    "medium_short = DistributedCircuit(frequency=freq,\n",
    "    R=rlcg_short[0], L=rlcg_short[1], C=rlcg_short[2], G=rlcg_short[3],\n",
    "    z0_port=z0_ref\n",
    ")\n",
    "line_short = medium_short.line(\n",
    "    d=1, unit='m'\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Modeling the Shunt Impedance\n",
    "\n",
    "Then, we need to model the shunt impedance of the open and short standards.\n",
    "For the open standard, it's a capacitance. For the short standard, it's\n",
    "an inductance.\n",
    "\n",
    "Both are modeled as third-degree polynomials, as functions of frequency.\n",
    "In `numpy`, one can quickly define such a function via\n",
    "`np.poly1d([x3, x2, x1, x0])`. This is a higher-order function which accepts\n",
    "a list of coefficients in descending order, and returns a callable polynomial\n",
    "function.\n",
    "\n",
    "After the polynomial is evaluated, we can generate the frequency-dependent\n",
    "capacitors and inductors. The open circuit is modeled as a series\n",
    "`medium.capacitor()` followed by an ideal `medium.short()`. The short circuit\n",
    "is modeled as a series `medium.inductor()` followed by an ideal\n",
    "`medium.short()`.\n",
    "\n",
    "According to [[15](SOLT%20Calibration%20Standards%20Creation.html#ref15)] the\n",
    "S-parameters of the capacitor and inductor are defined with respect to the\n",
    "system's reference impedance, not the *offset* transmission line, so we use\n",
    "`ideal_medium` to avoid confusion. Nevertheless, `medium_open` or `medium_short`\n",
    "are also acceptable: they also correctly use the port impedance of `skrf.Media()`'\n",
    "as the reference impedance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# use ideal_medium, not medium_open and medium_short to avoid\n",
    "# reference impedance confusions.\n",
    "\n",
    "capacitor_poly = np.poly1d([\n",
    "      13.400 * 1e-45,\n",
    "    -264.990 * 1e-36,\n",
    "    2536.800 * 1e-27,\n",
    "      89.939 * 1e-15\n",
    "])\n",
    "capacitor_list = capacitor_poly(freq.f)\n",
    "shunt_open = ideal_medium.capacitor(capacitor_list) ** ideal_medium.short()\n",
    "\n",
    "inductor_poly = np.poly1d([\n",
    "      -0.7847 * 1e-42,\n",
    "      34.8314 * 1e-33,\n",
    "    -496.4808 * 1e-24,\n",
    "       3.3998 * 1e-12\n",
    "])\n",
    "inductor_list = inductor_poly(freq.f)\n",
    "shunt_short = ideal_medium.inductor(inductor_list) ** ideal_medium.short()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Note on Series and Shunt Impedance\n",
    "\n",
    "To model a shunt impedance such as a capacitance for the open standard,\n",
    "one can use a series `medium.capacitor()` terminated by a `medium.short()`.\n",
    "\n",
    "\n",
    "    # [PORT] ---- [CAPACITOR] --- [PORT]\n",
    "    #                               |\n",
    "    #                               |\n",
    "    #                            [SHORT]\n",
    "    #                               |\n",
    "    #                               |\n",
    "    #                              GND\n",
    "    shunt_open = ideal_medium.capacitor(capacitor_list) ** ideal_medium.short()\n",
    "\n",
    "Or a parallel `medium.shunt_capacitor()` terminated by a `medium.open()`.\n",
    "\n",
    "    # [PORT] -----v----- [PORT] ----- [OPEN]\n",
    "    #             |\n",
    "    #             |\n",
    "    #     [SHUNT CAPACITOR]\n",
    "    #             |\n",
    "    #             |\n",
    "    #           [GND]\n",
    "    shunt_open = ideal_medium.shunt_capacitor(capacitor_list) ** ideal_medium.open()\n",
    "\n",
    "Both are equivalent. The `medium.open()` termination is important for\n",
    "`shunt_capacitor()`: it creates a two-port network with a capacitor to\n",
    "ground - this network is similar to an oscilloscope terminator, which is\n",
    "a 2-port device with a shunt 50 Ω resistance - so the other \"output\"\n",
    "port needs to be open. Otherwise, a line terminated solely by a\n",
    "`shunt_capacitor()` produces incorrect S-parameters."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Completion"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, we connect these model components together, and add definitions for the\n",
    "ideal load and Thru, this completes our modeling."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "open_std = line_open ** shunt_open\n",
    "short_std = line_short ** shunt_short\n",
    "load_std = ideal_medium.match()\n",
    "thru_std = ideal_medium.thru()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now you can pass these standards into scikit-rf's calibration routines, or use the `write_touchstone()` method to save them on the disk for future use.\n",
    "\n",
    ".. note::\n",
    "Here, the `open_std`, `short_std` and `load_std` we\n",
    "generated are one-port networks, but most scikit-rf's calibration routines expect a\n",
    "two-port networks as standards since they're used in two-port calibrations. You can\n",
    "use the function `skrf.two_port_reflect()` to generate a two-port network\n",
    "from two one-port networks. For more information, be sure to read the\n",
    "[SOLT calibration](./SOLT.ipynb) example in the doc."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Plotting"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "skrf.stylely()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, let's take a look at the magnitudes and phase shifts of our standards.\n",
    "\n",
    "#### Open"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(1, 2)\n",
    "fig.set_size_inches(12, 5)\n",
    "\n",
    "plt.suptitle(\"Keysight 85032F Plug Open (S11)\")\n",
    "open_std.plot_s_db(ax=ax[0], color='red', label=\"Magnitude\")\n",
    "open_std.plot_s_deg_unwrap(ax=ax[1], color='blue', label=\"Phase\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Short"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(1, 2)\n",
    "fig.set_size_inches(12, 5)\n",
    "\n",
    "plt.suptitle(\"Keysight 85032F Plug Short (S11)\")\n",
    "short_std.plot_s_db(ax=ax[0], color='red', label=\"Magnitude\")\n",
    "short_std.plot_s_deg_unwrap(ax=ax[1], color='blue', label=\"Phase\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Conclusion\n",
    "\n",
    "As shown in the graphs above, the losses in the standards are extremely low, on the\n",
    "order of 0.01 dB throughout the spectrum. Meanwhile, the phase shift is what really\n",
    "needs compensation for. At 1 GHz, the phase shift has already reached 25 degrees or\n",
    "so.\n",
    "\n",
    ".. important::\n",
    "   Most lab-grade standards are offset standards, so large phase shifts are unavoidable.\n",
    "   Always use these standards with the correct definitions entered."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Code Snippet\n",
    "\n",
    "For convenience, you can reuse the following code snippets to generate calibration standard networks from coefficients in Keysight format."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import skrf\n",
    "from skrf.media import DefinedGammaZ0, DistributedCircuit\n",
    "\n",
    "\n",
    "def keysight_calkit_offset_line(freq, offset_delay, offset_loss, offset_z0, ref_z0):\n",
    "    if offset_delay or offset_loss:\n",
    "        r = offset_loss * offset_delay * np.sqrt(freq.f / 1e9)\n",
    "        l = (offset_delay * offset_z0) + r / (2 * np.pi * freq.f)\n",
    "        c = offset_delay / offset_z0\n",
    "        g = 0\n",
    "\n",
    "        medium = DistributedCircuit(frequency=freq,\n",
    "            R=r, L=l, C=c, G=g,\n",
    "            z0_port=ref_z0\n",
    "        )\n",
    "        offset_line = medium.line(d=1, unit='m')\n",
    "        return medium, offset_line\n",
    "    else:\n",
    "        medium = DefinedGammaZ0(frequency=freq, z0=ref_z0)\n",
    "        line = medium.line(d=0)\n",
    "        return medium, line\n",
    "\n",
    "\n",
    "def keysight_calkit_open(freq, offset_delay, offset_loss, c0, c1, c2, c3, offset_z0, ref_z0):\n",
    "    # Capacitance is defined with respect to the system reference impedance ref_z0, not the\n",
    "    # lossy line impedance. In scikit-rf, the return values of `shunt_capacitor()` and\n",
    "    # `medium.open()` methods are (correctly) referenced to z0_port, which has been set to\n",
    "    # ref_z0.\n",
    "    medium, line = keysight_calkit_offset_line(freq, offset_delay, offset_loss, offset_z0, ref_z0)\n",
    "    if c0 or c1 or c2 or c3:\n",
    "        poly = np.poly1d([c3, c2, c1, c0])\n",
    "        capacitance = medium.shunt_capacitor(poly(freq.f)) ** medium.open()\n",
    "    else:\n",
    "        capacitance = medium.open()\n",
    "    return line ** capacitance\n",
    "\n",
    "\n",
    "def keysight_calkit_short(freq, offset_delay, offset_loss, l0, l1, l2, l3, offset_z0, ref_z0):\n",
    "    # Inductance is defined with respect to the system reference impedance ref_z0, not the\n",
    "    # lossy line impedance. In scikit-rf, the return values of `shunt_inductance()` and\n",
    "    # `medium.short()` methods are (correctly) referenced to z0_port, which has been set to\n",
    "    # ref_z0.\n",
    "    medium, line = keysight_calkit_offset_line(freq, offset_delay, offset_loss, offset_z0, ref_z0)\n",
    "    if l0 or l1 or l2 or l3:\n",
    "        poly = np.poly1d([l3, l2, l1, l0])\n",
    "        inductance = medium.inductor(poly(freq.f)) ** medium.short()\n",
    "    else:\n",
    "        inductance = medium.short()\n",
    "    return line ** inductance\n",
    "\n",
    "\n",
    "def keysight_calkit_load(freq, offset_delay=0, offset_loss=0, offset_z0=50, ref_z0=50):\n",
    "    medium, line = keysight_calkit_offset_line(freq, offset_delay, offset_loss, offset_z0, ref_z0)\n",
    "    ideal_medium = DefinedGammaZ0(frequency=freq, z0=ref_z0)\n",
    "    load = ideal_medium.match()\n",
    "    return line ** load\n",
    "\n",
    "\n",
    "def keysight_calkit_thru(freq, offset_delay=0, offset_loss=0, offset_z0=50, ref_z0=50):\n",
    "    medium, line = keysight_calkit_offset_line(freq, offset_delay, offset_loss, offset_z0, ref_z0)\n",
    "    thru = medium.thru()\n",
    "    return line ** thru"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Example: Keysight 85033E\n",
    "\n",
    "To show another usage example of the code snippet above, we're going to model\n",
    "the Keysight 85033E, 3.5 mm, 50 Ω, DC to 9 GHz calibration kit (plug), with\n",
    "the following coefficients. [[22](SOLT%20Calibration%20Standards%20Creation.html#ref22)]\n",
    "This is one of the most widely used 3.5 mm calibration standards across labs.\n",
    "\n",
    "|    Parameter    |            Unit             |    Open   |   Short   |  Load   |   Thru   |\n",
    "| --------------- | --------------------------- | --------- | --------- | ------- | -------- |\n",
    "| $\\text{C}_0$    |  $10^{-15} \\text{ F}$       |   49.433  |           |         |          |\n",
    "| $\\text{C}_1$    |  $10^{-27} \\text{ F/Hz}$    |  -310.13  |           |         |          |\n",
    "| $\\text{C}_2$    |  $10^{-36} \\text{ F/Hz}^2$  |   23.168  |           |         |          |\n",
    "| $\\text{C}_3$    |  $10^{-45} \\text{ F/Hz}^3$  | -0.15966  |           |         |          |\n",
    "| $\\text{L}_0$    |  $10^{-12} \\text{ H}$       |           |  2.0765   |         |          |\n",
    "| $\\text{L}_1$    |  $10^{-24} \\text{ H/Hz}$    |           | -108.54   |         |          |\n",
    "| $\\text{L}_2$    |  $10^{-33} \\text{ H/Hz}^2$  |           |  2.1705   |         |          |\n",
    "| $\\text{L}_3$    |  $10^{-42} \\text{ H/Hz}^3$  |           |   -0.01   |         |          |\n",
    "|  Resistance     |         $\\Omega$            |           |           |    50   |          |\n",
    "| Offset Delay    |            ps               |  29.243   | 31.785    |     0   |     0    |\n",
    "| Offset Loss     | $\\text{G}\\Omega$ / s        |    2.2    |    2.36   |   2.3   |   2.3    |\n",
    "| Offset $Z_0$    |         $\\Omega$            |    50     |     50    |    50   |    50    |\n",
    "| Reference $Z_0$ |         $\\Omega$            |    50     |     50    |    50   |    50    |\n",
    "\n",
    ".. note::\n",
    "    Although Keysight specified offset losses for the Load and Thru standards, they have offset\n",
    "    delays of 0, effectively removing the transmission line, hence these losses should be ignored.\n",
    "    The offset Thru standard is meant to be an unknown or flush Thru.\n",
    "    \n",
    ".. important::\n",
    "    Keysight 85033E's plug and socket calibration standards have mostly identical parameters,\n",
    "    but one difference: the Open standard (socket) has an offset loss of 2.3 GΩ/s, but the\n",
    "    Open standard (plug) has an offset loss of 2.2 GΩ/s.\n",
    "\n",
    "#### Creation\n",
    "\n",
    "To create this calibration kit using the above code snippet:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "freq = skrf.Frequency(1, 9000, 1001, \"MHz\")\n",
    "keysight_85033e_plug_open_std = keysight_calkit_open(\n",
    "    freq,\n",
    "    offset_delay=29.243e-12, offset_loss=2.2e9,\n",
    "    c0=49.433e-15, c1=-310.13e-27, c2=23.168e-36, c3=-0.15966e-45,\n",
    "    offset_z0=50, ref_z0=50\n",
    ")\n",
    "keysight_85033e_plug_short_std = keysight_calkit_short(\n",
    "    freq,\n",
    "    offset_delay=31.785e-12, offset_loss=2.36e9,\n",
    "    l0=2.0765e-12, l1=-108.54e-24, l2=2.1705e-33, l3=-0.01e-42,\n",
    "    offset_z0=50, ref_z0=50\n",
    ")\n",
    "load_std = keysight_calkit_load(freq)\n",
    "thru_std = keysight_calkit_thru(freq)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Plotting\n",
    "\n",
    "##### Open"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(1, 2)\n",
    "fig.set_size_inches(12, 5)\n",
    "\n",
    "plt.suptitle(\"Keysight 85033E Plug Open (S11)\")\n",
    "keysight_85033e_plug_open_std.plot_s_db(ax=ax[0], color='red', label=\"Magnitude\")\n",
    "keysight_85033e_plug_open_std.plot_s_deg_unwrap(ax=ax[1], color='blue', label=\"Phase\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##### Short"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(1, 2)\n",
    "fig.set_size_inches(12, 5)\n",
    "\n",
    "plt.suptitle(\"Keysight 85033E Plug Short (S11)\")\n",
    "keysight_85033e_plug_short_std.plot_s_db(ax=ax[0], color='red', label=\"Magnitude\")\n",
    "keysight_85033e_plug_short_std.plot_s_deg_unwrap(ax=ax[1], color='blue', label=\"Phase\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Conclusion\n",
    "\n",
    "Again, entering the correct offset delay is critical when using an offset\n",
    "calibration standard."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Example: Generic SMA Plug Calkit\n",
    "\n",
    "And now for something completely different. The author has measured the typical\n",
    "parameters of generic SMA standards with the following data using NanoRFE VNA6000\n",
    "with respect to the Keysight 85033E calibration standard. The numerically fitted\n",
    "circuit paramaters are shown below.\n",
    "\n",
    "|    Parameter    |            Unit             |  Flush Open  |   Flush Open  |   Thru Open   |\n",
    "| --------------- | --------------------------- | ------------ | --------------| ------------- |\n",
    "| VNA Connector   |                             |   SMA Socket | 3.5 mm Socket |    SMA Plug   |\n",
    "| DUT Connector   |                             |   SMA Plug   |    SMA Plug   | SMA Thru+Plug |\n",
    "| $\\text{C}_0$    |  $10^{-15} \\text{ F}$       |   13.670     |    28.065     |    13.670     |\n",
    "| Offset Delay    |            ps               |       0      |       0       |    47.08      | \n",
    "| Offset $Z_0$    |         $\\Omega$            |      50      |      50       |      50       |\n",
    "| Reference $Z_0$ |         $\\Omega$            |      50      |      50       |      50       |\n",
    "\n",
    "The generic *Short* is essentially a flush short, so only the generic *Open*\n",
    "standard is shown. Due to measurement noise, only a linear fit has been\n",
    "performed. All the above results are preliminary, and has not yet undergone\n",
    "rigorous verification.\n",
    "\n",
    "#### Creation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "freq = skrf.Frequency(1, 9000, 1001, \"MHz\")\n",
    "generic_sma_plug_on_sma_socket_open_std = keysight_calkit_open(\n",
    "    freq,\n",
    "    offset_delay=0, offset_loss=0,\n",
    "    c0=13.670e-15, c1=0, c2=0, c3=0,\n",
    "    offset_z0=50, ref_z0=50\n",
    ")\n",
    "generic_sma_plug_on_apc35_socket_open_std = keysight_calkit_open(\n",
    "    freq,\n",
    "    offset_delay=0, offset_loss=0,\n",
    "    c0=28.065e-15, c1=0, c2=0, c3=0,\n",
    "    offset_z0=50, ref_z0=50\n",
    ")\n",
    "generic_sma_plug_on_sma_socket_thru_open_std = keysight_calkit_open(\n",
    "    freq,\n",
    "    offset_delay=47.08e-12, offset_loss=0,\n",
    "    c0=13.670e-15, c1=0, c2=0, c3=0,\n",
    "    offset_z0=50, ref_z0=50\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Plotting\n",
    "\n",
    "##### Flush Open"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(1, 2)\n",
    "fig.set_size_inches(12, 5)\n",
    "\n",
    "ax[0].set_title(\"Generic SMA Open Plug Phase (S11)\")\n",
    "generic_sma_plug_on_sma_socket_open_std.plot_s_deg_unwrap(ax=ax[0], color='red', label=\"SMA Port\")\n",
    "generic_sma_plug_on_apc35_socket_open_std.plot_s_deg_unwrap(ax=ax[0], color='blue', label=\"3.5 mm Port\")\n",
    "\n",
    "ax[1].set_title(\"Thru + SMA Plug Open Phase (S11)\")\n",
    "generic_sma_plug_on_sma_socket_thru_open_std.plot_s_deg_unwrap(ax=ax[1], color='red', label=\"SMA Port\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "#### Conclusion\n",
    "\n",
    "When a generic SMA standard is used to calibrate an SMA socket, the phase\n",
    "error is less than 10 degrees in comparison to an ideal open standard.\n",
    "This justifies the ideal assumption for non-critical measurements.\n",
    "\n",
    "In comparison, when the generic SMA open standard is cascaded with a\n",
    "generic Thru adapter to form an SMA socket, this effectively converts the\n",
    "standard to an *offset* standard, with a phase deviation in excess of\n",
    "300 degrees. When calibrating SMA plugs and coaxial cables, the ideal\n",
    "assumption creates large measurement errors. This can be corrected by\n",
    "characterizing the *offset delay* of the Thru standard, equivalent to\n",
    "applying a port extension to all measurements.\n",
    "\n",
    "When the same generic open standard is measured on SMA and 3.5 mm sockets,\n",
    "significant differences are observed. The author believes a true physical\n",
    "fringe capacitance difference is unlikely. It's more likely due to circuit\n",
    "parasitics, such as the shift of a connector's effective reference plane\n",
    "between its *connected* and *unconnected state* (the calibration is only\n",
    "valid for the *connected* port, not an unterminated one).\n",
    "[[2](SOLT%20Calibration%20Standards%20Creation.html#ref2)]\n",
    "Connector parasitics such as pin depths\n",
    "[[16](SOLT%20Calibration%20Standards%20Creation.html#ref16)][[17](SOLT%20Calibration%20Standards%20Creation.html#ref17)]\n",
    "and step capacitances in a\n",
    "cross-mated 3.5 mm and SMA/2.92 mm pair are likely also involved.\n",
    "[[8](SOLT%20Calibration%20Standards%20Creation.html#ref8)]\n",
    "\n",
    "This is the possible reason that a pinless hollow open standard is no longer\n",
    "used in RF measurements (though they found historical metrology uses in APC-7\n",
    "and Type-N connectors with acceptable results). The lack of a center conductor\n",
    "makes the electrical characteristic of the open standard unstable and\n",
    "port-dependent.\n",
    "\n",
    ".. tip::\n",
    "   HP 85032B/E Type-N standard was a historical example with a pinless\n",
    "   Open. Kurt Poulsen (OZ7OU) discovered that most\n",
    "   generic Type-N standards (plug) circulating on the market are its clones\n",
    "   [[23](SOLT%20Calibration%20Standards%20Creation.html#ref23)],\n",
    "   allowing one to use HP's definition [[22](SOLT%20Calibration%20Standards%20Creation.html#ref22)]\n",
    "   in non-critical calibration.\n",
    "   However, this is not applicable to socket types due to the lack\n",
    "   of matching center conductor extenders."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Rohde & Schwarz / Anritsu Coefficient Format\n",
    "\n",
    "On Rohde & Schwarz and Anritsu VNAs, a slightly different format is used to define the coefficients. Here's an example of a Maury Microwave 8050CK10, a 3.5 mm, DC to 26.5 GHz calibration kit defined in Rohde & Schwarz's format.\n",
    "[[19](SOLT%20Calibration%20Standards%20Creation.html#ref19)]\n",
    "\n",
    "|   Parameter  |            Unit             |    Open    |   Short   |  Load   |   Thru   |\n",
    "| ------------ | --------------------------- | ---------- | --------- | ------- | -------- |\n",
    "| $\\text{C}_0$ |  $10^{-15} \\text{ F}$       |  62.54     |           |         |          |\n",
    "| $\\text{C}_1$ |  $10^{-15} \\text{ F/GHz}$   |  1284.0    |           |         |          |\n",
    "| $\\text{C}_2$ |  $10^{-15} \\text{ F/GHz}^2$ |   107.6    |           |         |          |\n",
    "| $\\text{C}_3$ |  $10^{-15} \\text{ F/GHz}^3$ |  -1.886    |           |         |          |\n",
    "| $\\text{L}_0$ |  $10^{-12} \\text{ H}$       |            |    0      |         |          |\n",
    "| $\\text{L}_1$ |  $10^{-12} \\text{ H/GHz}$   |            |    0      |         |          |\n",
    "| $\\text{L}_2$ |  $10^{-12} \\text{ H/GHz}^2$ |            |    0      |         |          |\n",
    "| $\\text{L}_3$ |  $10^{-12} \\text{ H/GHz}^3$ |            |    0      |         |          |\n",
    "|  Resistance  |         $\\Omega$            |            |           |    50   |          |\n",
    "| Offset Length|            mm               |  4.344     |  5.0017   |     0   |  17.375  |\n",
    "| Offset Loss  |$\\text{dB / }\\sqrt{\\text{GHz}}$| 0.0033   |  0.0038   |     0   |   0.0065 |\n",
    "\n",
    "### Modeling the Offset Transmission Line\n",
    "\n",
    "As shown, it's essentially the same circuit model, the only difference is that the offset transmission line is defined in different units of measurements: offset delay is defined as a physical length instead of a time delay, offset loss is defined in decibel. The offset $Z_0$ is defined to be the system impedance $Z_\\text{ref}$ (50 Ω or 75 Ω), thus unlisted.\n",
    "\n",
    ".. tip::\n",
    "  Note that a signal travels in both directions in a $S_{11}$ measurement, so the loss from $S_{11}$ in decibel is doubled\n",
    "  numerically in comparison to the stated offset loss.\n",
    "\n",
    "We can reuse the same calculations in the Keysight model after a simple unit conversion using these equations.\n",
    "[[15](SOLT%20Calibration%20Standards%20Creation.html#ref15)]\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "Z_\\text{off} &= Z_\\text{ref} \\\\\n",
    "t_\\text{delay} &= \\frac{D \\cdot \\sqrt{\\epsilon_r}}{c_0} \\\\\n",
    "A_\\text{loss} &= \\frac{L \\cdot Z_0}{t_\\text{delay} \\cdot 20 \\log_{10}{(\\mathrm{e})}}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "where $D$ and $L$ are the offset length (meter) and offset loss ($\\text{dB / }\\sqrt{\\text{GHz}}$) in the R&S model, $t_\\text{delay}$ and $A_\\text{loss}$ are the offset delay (second) and offset loss ($\\Omega$ / s) in Keysight's model, $\\epsilon_r$ is the dielectric constant, it's air by definition, thus $\\epsilon_r = 1$, and $c_0$ is the speed of light. The term $20 \\log_{10}{(\\mathrm{e})}$ is a conversion from decibel to neper."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def rs_to_keysight(rs_offset_length, rs_offset_loss, offset_z0=50):\n",
    "    offset_delay = rs_offset_length / skrf.constants.c\n",
    "    offset_loss = skrf.mathFunctions.db_2_np(rs_offset_loss * offset_z0 / offset_delay)\n",
    "    return offset_delay, offset_loss"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "After unit conversion, we can define standards just like how calibration standards in Keysight-style\n",
    "coefficients are defined."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "z0_ref = 50\n",
    "\n",
    "offset_delay, offset_loss = rs_to_keysight(4.344e-3, 0.0033)\n",
    "rlcg_open = offset_rlcg(freq, offset_delay, offset_loss, z0_ref)\n",
    "medium_open = DistributedCircuit(frequency=freq,\n",
    "    R=rlcg_open[0], L=rlcg_open[1], C=rlcg_open[2], G=rlcg_open[3],\n",
    "    z0_port=z0_ref\n",
    ")\n",
    "line_open = medium_open.line(\n",
    "    d=1, unit='m'\n",
    ")\n",
    "\n",
    "offset_delay, offset_loss = rs_to_keysight(5.0017e-3, 0.0038)\n",
    "rlcg_short = offset_rlcg(freq, offset_delay, offset_loss, z0_ref)\n",
    "medium_short = DistributedCircuit(frequency=freq,\n",
    "    R=rlcg_short[0], L=rlcg_short[1], C=rlcg_short[2], G=rlcg_short[3],\n",
    "    z0_port=z0_ref\n",
    ")\n",
    "line_short = medium_short.line(\n",
    "    d=1, unit='m'\n",
    ")\n",
    "\n",
    "offset_delay, offset_loss = rs_to_keysight(17.375e-3, 0.0065)\n",
    "rlcg_thru = offset_rlcg(freq, offset_delay, offset_loss, z0_ref)\n",
    "medium_thru = DistributedCircuit(frequency=freq,\n",
    "    R=rlcg_thru[0], L=rlcg_thru[1], C=rlcg_thru[2], G=rlcg_thru[3],\n",
    "    z0_port=z0_ref\n",
    ")\n",
    "line_thru = medium_thru.line(\n",
    "    d=1, unit='m'\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Modeling the Shunt Impedance\n",
    "\n",
    "The definition of shunt impedance is identical to the Keysight format.\n",
    "\n",
    "But, beware of the units used for the capacitance and inductance! In the\n",
    "given table, the capacitances are given in $10^{-15} \\text{ F}$, $10^{-15} \\text{ F/GHz}$,\n",
    "$10^{-15} \\text{ F/GHz}^2$, and\n",
    "$10^{-15} \\text{ F/GHz}^3$. For Keysight and Anritsu VNAs, they're given in $10^{-15} \\text{ F}$,\n",
    "$10^{-27} \\text{ F/Hz}$, $10^{-36} \\text{ F/Hz}^2$ and $10^{-45} \\text{ F/Hz}^3$. Inductance\n",
    "units have the same differences. Always double-check the units before start modeling. To\n",
    "convert the units from the first to the second format, multiply $x_1$, $x_2$ and $x_3$\n",
    "by 1000 (don't change the constant term $x_0$). For consistency, we'll use the second format\n",
    "in the code.\n",
    "\n",
    "Since the inductance in the short standard is neglected, only the capacitance in the open\n",
    "standard is modeled, the short is modeled as ideal."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "capacitor_poly = np.poly1d([\n",
    "    -0.001886 * 1000e-45,\n",
    "     0.1076   * 1000e-36,\n",
    "    -1.284    * 1000e-27,\n",
    "    62.54     * 1e-15\n",
    "])\n",
    "capacitor_open = capacitor_poly(freq.f)\n",
    "shunt_open = ideal_medium.shunt_capacitor(capacitor_open) ** ideal_medium.open()\n",
    "# or: shunt_open = ideal_medium.capacitor(capacitor_open) ** ideal_medium.short()\n",
    "# see the Keysight example for explanation.\n",
    "\n",
    "shunt_short = ideal_medium.short()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Completion\n",
    "\n",
    "Finally, we connect these model components together."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "open_std = line_open ** shunt_open\n",
    "short_std = line_short ** shunt_short\n",
    "load_std = ideal_medium.match()\n",
    "thru_std = line_thru"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Plotting\n",
    "\n",
    "Again, let's examine the behaviors of the finished standards.\n",
    "\n",
    "#### Open"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(1, 2)\n",
    "fig.set_size_inches(12, 5)\n",
    "\n",
    "plt.suptitle(\"Maury Microwave 8050CK10 Open (S11)\")\n",
    "open_std.plot_s_db(ax=ax[0], color='red', label=\"Magnitude\")\n",
    "open_std.plot_s_deg_unwrap(ax=ax[1], color='blue', label=\"Phase\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Short"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(1, 2)\n",
    "fig.set_size_inches(12, 5)\n",
    "\n",
    "plt.suptitle(\"Maury Microwave 8050CK10 Short (S11)\")\n",
    "short_std.plot_s_db(ax=ax[0], color='red', label=\"Magnitude\")\n",
    "short_std.plot_s_deg_unwrap(ax=ax[1], color='blue', label=\"Phase\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Thru"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(1, 2)\n",
    "fig.set_size_inches(12, 5)\n",
    "\n",
    "plt.suptitle(\"Maury Microwave 8050CK10 Thru (S21)\")\n",
    "thru_std.s21.plot_s_db(ax=ax[0], color='red', label=\"Magnitude\")\n",
    "thru_std.s21.plot_s_deg_unwrap(ax=ax[1], color='blue', label=\"Phase\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Conclusion\n",
    "\n",
    "The results are similar to the Keysight calibration standards and the generic Thru\n",
    "adapter. The S21 graph for the Thru standard explains why adding an electrical\n",
    "delay sometimes can serve as a crude but usable calibration method (\"port extension\")\n",
    "for VNA measurements. Again, losses are extremely low, phase shift is the source of\n",
    "non-ideal properties in the *offset* standards. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Code Snippet\n",
    "\n",
    "For convenience, you can reuse the following code snippet to generate calibration standard networks from coefficients in Rohde & Schwarz and Anritsu format.\n",
    "\n",
    "```python\n",
    "import numpy as np\n",
    "\n",
    "import skrf\n",
    "from skrf.media import DefinedGammaZ0\n",
    "\n",
    "\n",
    "def keysight_calkit_offset_line(freq, offset_delay, offset_loss, offset_z0, ref_z0):\n",
    "    if offset_delay or offset_loss:\n",
    "        r = offset_loss * offset_delay * np.sqrt(freq.f / 1e9)\n",
    "        l = (offset_delay * offset_z0) + r / (2 * np.pi * freq.f)\n",
    "        c = offset_delay / offset_z0\n",
    "        g = 0\n",
    "\n",
    "        medium = DistributedCircuit(frequency=freq,\n",
    "            R=r, L=l, C=c, G=g,\n",
    "            z0_port=ref_z0\n",
    "        )\n",
    "        offset_line = medium.line(d=1, unit='m')\n",
    "        return medium, offset_line\n",
    "    else:\n",
    "        medium = DefinedGammaZ0(frequency=freq, z0=ref_z0)\n",
    "        line = medium.line(d=0)\n",
    "        return medium, line\n",
    "\n",
    "\n",
    "def rs_calkit_offset_line(freq, rs_offset_length, rs_offset_loss, offset_z0, ref_z0):\n",
    "    offset_delay = rs_offset_length / skrf.constants.c\n",
    "    if offset_delay != 0:\n",
    "        offset_loss = skrf.mathFunctions.db_2_np(rs_offset_loss * offset_z0 / offset_delay)\n",
    "    else:\n",
    "        offset_loss = 0\n",
    "    return keysight_calkit_offset_line(freq, offset_delay, offset_loss, offset_z0, ref_z0)\n",
    "\n",
    "\n",
    "def rs_calkit_open(freq, offset_length, offset_loss, c0, c1, c2, c3, offset_z0, ref_z0):\n",
    "    # Capacitance is defined with respect to the system reference impedance ref_z0, not the\n",
    "    # lossy line impedance. In scikit-rf, the return values of `shunt_capacitor()` and\n",
    "    # `medium.open()` methods are (correctly) referenced to z0_port, which has been set to\n",
    "    # ref_z0.\n",
    "    medium, line = rs_calkit_offset_line(freq, offset_length, offset_loss, offset_z0, ref_z0)\n",
    "    if c0 or c1 or c2 or c3:\n",
    "        poly = np.poly1d([c3, c2, c1, c0])\n",
    "        capacitance = medium.shunt_capacitor(poly(freq.f)) ** medium.open()\n",
    "    else:\n",
    "        capacitance = medium.open()\n",
    "    return line ** capacitance\n",
    "\n",
    "\n",
    "def rs_calkit_short(freq, offset_length, offset_loss, l0, l1, l2, l3, offset_z0, ref_z0):\n",
    "    # Inductance is defined with respect to the system reference impedance ref_z0, not the\n",
    "    # lossy line impedance. In scikit-rf, the return values of `shunt_inductance()` and\n",
    "    # `medium.short()` methods are (correctly) referenced to z0_port, which has been set to\n",
    "    # ref_z0.\n",
    "    medium, line = rs_calkit_offset_line(freq, offset_length, offset_loss, offset_z0, ref_z0)\n",
    "    if l0 or l1 or l2 or l3:\n",
    "        poly = np.poly1d([l3, l2, l1, l0])\n",
    "        inductance = medium.inductor(poly(freq.f)) ** medium.short()\n",
    "    else:\n",
    "        inductance = medium.short()\n",
    "    return line ** inductance\n",
    "\n",
    "\n",
    "def rs_calkit_load(freq, offset_length, offset_loss, offset_z0, ref_z0):\n",
    "    medium, line = rs_calkit_offset_line(freq, offset_length, offset_loss, ref_z0, ref_z0)\n",
    "    load = medium.match()\n",
    "    return line ** load\n",
    "\n",
    "\n",
    "def rs_calkit_thru(freq, offset_length, offset_loss, offset_z0, ref_z0):\n",
    "    medium, line = rs_calkit_offset_line(freq, offset_length, offset_loss, offset_z0, ref_z0)\n",
    "    thru = medium.thru()\n",
    "    return line ** thru\n",
    "\n",
    "\n",
    "freq = skrf.Frequency(1, 9000, 1001, \"MHz\")\n",
    "open_std = rs_calkit_open(\n",
    "    freq,\n",
    "    offset_length=4.344e-3, offset_loss=0.0033,\n",
    "    offset_z0=50, ref_z0=50,\n",
    "    # Due to unit differences, the numerical values of c1, c2 and c3\n",
    "    # must be multiplied by 1000 from the R&S datasheet value. For\n",
    "    # Anritsu, this is not needed. Check the units on your datasheet!\n",
    "    c0=62.54     * 1e-15,\n",
    "    c1=-1.284    * 1000e-27,\n",
    "    c2=0.1076    * 1000e-36,\n",
    "    c3=-0.001886 * 1000e-45,\n",
    ")\n",
    "short_std = rs_calkit_short(\n",
    "    freq,\n",
    "    offset_length=5.0017e-3, offset_loss=0.0038,\n",
    "    offset_z0=50, ref_z0=50,\n",
    "    l0=0, l1=0, l2=0, l3=0\n",
    ")\n",
    "load_std = rs_calkit_load(\n",
    "    freq,\n",
    "    offset_length=0, offset_loss=0,\n",
    "    offset_z0=50, ref_z0=50,\n",
    ")\n",
    "thru_std = rs_calkit_thru(\n",
    "    freq,\n",
    "    offset_length=17.375e-3, offset_loss=0.0065,\n",
    "    offset_z0=50, ref_z0=50,\n",
    ")\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Appendix: Derivation of Offset Transmission Line $\\gamma$ and $Z_c$\n",
    "\n",
    "### Derivation of RLCG Line Parameters\n",
    "\n",
    "According to Keysight [[4](SOLT%20Calibration%20Standards%20Creation.html#ref4)],\n",
    "equation 2B.3, the *offset* transmission line parameters are defined with respect\n",
    "to a physical coaxial line using the following relations:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "A_\\text{loss} &= \\dfrac{R_0 v}{\\sqrt{\\epsilon_r \\frac{f}{\\text{1 GHz}}}} \\\\\n",
    "t_\\text{delay} &= \\dfrac{l \\sqrt{\\epsilon_r}}{v} = \\sqrt{L_0 C} \\\\\n",
    "Z_\\text{off} &= \\frac{\\mu_0 v}{2 \\pi \\sqrt{\\epsilon_r}} \\ln(\\dfrac{D}{d}) = \\sqrt{\\frac{L_0}{C}}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "where $v = c_0$ [[13](SOLT%20Calibration%20Standards%20Creation.html#ref13)]\n",
    "is the speed of light, $\\mu_0$ is the vacuum permeability\n",
    "$\\epsilon_r$ is the medium's relative permittivity, $R_0$ and $L_0$ are the\n",
    "distributed nominal resistance and perfect-conductor inductance, $C$ is the distributed\n",
    "capacitance, $D$ and $d$ are the outer and inner diameters of the coaxial\n",
    "line.\n",
    "\n",
    "By algebraic manipulation:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\left( R_0 \\sqrt{\\dfrac{f}{10^9}} \\right) l &= A_\\text{loss} t_\\text{delay} = \\dfrac{R_0 v}{\\sqrt{\\epsilon_r \\frac{f}{10^9}}} \\dfrac{l \\sqrt{\\epsilon_r}}{v} \\\\\n",
    "L_0 &= t_\\text{delay}  Z_\\text{off} = \\sqrt{L_0 C} \\sqrt{\\frac{L_0}{C}}  \\\\\n",
    "C &= \\dfrac{t_\\text{delay}}{Z_\\text{off}} = \\dfrac{\\sqrt{L_0 C}}{\\sqrt{\\frac{L_0}{C}}} \\\\\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Finally, apply definitions in [[4](SOLT%20Calibration%20Standards%20Creation.html#ref4)],\n",
    "equation 2B.2:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "G &= 0 \\\\\n",
    "L &= L_0 + \\dfrac{R}{\\omega}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "The full RLCG line parameters are derived as follows:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "R(f) l &= \\left(A_\\text{loss} t_\\text{delay}\\right) \\cdot \\sqrt{\\frac{f}{10^9}} \\\\\n",
    "L(f) &= L_0 + L_\\text{cond} = t_\\text{delay}  Z_\\text{off} + \\dfrac{R}{2\\pi f} \\\\\n",
    "C &= \\dfrac{t_\\text{delay}}{Z_\\text{off}} \\\\\n",
    "G &= 0 \\\\\n",
    "l &= 1\\text{ (normalized)}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "### Keysight's Low-Loss Approximation\n",
    "\n",
    "One can calculate the line constant using the general formula from RLCG\n",
    "parameters.\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "Z_c &= \\sqrt{ \\frac{R + j \\omega L}{G + j \\omega C}} \\\\\n",
    "\\gamma &= \\sqrt{(R + j \\omega L) (G + j \\omega C)}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "This is the method used in scikit-rf's `DistributedCircuit`\n",
    "in general.\n",
    "\n",
    "However, Keysight's official publication\n",
    "[[4](SOLT%20Calibration%20Standards%20Creation.html#ref4)]\n",
    "and other works\n",
    "[[15](SOLT%20Calibration%20Standards%20Creation.html#ref15)][[20](SOLT%20Calibration%20Standards%20Creation.html#ref20)]\n",
    "used a low-loss approximation to calculate the line constants.\n",
    "We show both the formulas and code to manually calculate $\\gamma$\n",
    "and $Z_c$ using the same low-loss approximation. This can be useful\n",
    "if identical numerical values to the last digit are absolutely\n",
    "required.\n",
    "\n",
    "Regroup the terms in $\\gamma$'s expression (see\n",
    "[[21](SOLT%20Calibration%20Standards%20Creation.html#ref21)], equation\n",
    "2.83):\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\gamma &= \\sqrt{(R + j \\omega L) (G + j \\omega C)} \\\\\n",
    "       &= j\\omega \\sqrt{LC} \\sqrt{1 - j \\left(\\frac{R}{\\omega L} + \\dfrac{G}{\\omega C} \\right) - \\frac{RG}{\\omega^2 LC}}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "When $G=0$:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "Z_c &= \\sqrt{ \\frac{R + j \\omega L}{j \\omega C}} \\\\\n",
    "\\gamma &= j\\omega \\sqrt{LC} \\sqrt{1 - j \\left(\\frac{R}{\\omega L}\\right)}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Since $L = L_0 + \\frac{R}{\\omega}$:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "Z_c &= \\sqrt{ \\frac{R + j \\omega \\left( L_0 + \\frac{R}{\\omega} \\right) }{j \\omega C}} \\\\\n",
    "    &= \\sqrt{\\dfrac{L_0}{C}} \\sqrt{1 + \\dfrac{R}{\\omega L_0} (1 - j)} \\\\\n",
    "\\gamma &= j\\omega \\sqrt{\\left( L_0 + \\frac{R}{\\omega} \\right) C} \\sqrt{1 - j \\left(\\frac{R}{\\omega \\left( L_0 + \\frac{R}{\\omega} \\right)}\\right)} \\\\\n",
    "       &= j\\omega \\sqrt{L_0 C} \\sqrt{1 + \\dfrac{R}{\\omega L_0} (1 - j)}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Apply the first-order Taylor expansion $\\sqrt{1 + x} = 1 + x/2$\n",
    "[[20](SOLT%20Calibration%20Standards%20Creation.html#ref20)]:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\gamma &= j \\omega \\sqrt{L_0 C} \\left[1 + (1 - j) \\dfrac{R}{2\\omega L_0}\\right] \\\\\n",
    "Z_c &= \\sqrt{\\dfrac{L_0}{C}} \\left[1 + (1 - j) \\dfrac{R}{2 \\omega L_0}\\right]\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "By plugging $R$, $L_0$, $C$ into these equations, one can eventually obtain the\n",
    "official formulas in [[4](SOLT%20Calibration%20Standards%20Creation.html#ref4)]:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\alpha l &= \\dfrac{A_\\text{loss} t_\\text{delay}}{2 Z_\\text{off}} \\sqrt{\\dfrac{f}{10^9}} \\\\\n",
    "\\beta l &= \\omega t_\\text{delay} + \\alpha l \\\\\n",
    "\\gamma l &= \\alpha l + j \\beta l \\\\\n",
    "Z_c &= Z_\\text{off} + (1 - 1j) \\left(\\frac{A_\\text{loss}}{4 \\pi f}\\right) \\sqrt{\\frac{f}{10^9}}\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "### Implementation\n",
    "\n",
    "The following code implements Keysight's method for calculating line constants.\n",
    "\n",
    "```python\n",
    "def offset_gamma_and_zc(offset_delay, offset_loss, offset_z0):\n",
    "    alpha_l = (offset_loss * offset_delay) / (2 * offset_z0)\n",
    "    alpha_l *= np.sqrt(freq.f / 1e9)\n",
    "    beta_l = 2 * np.pi * freq.f * offset_delay + alpha_l\n",
    "    gamma_l = alpha_l + 1j * beta_l\n",
    "    zc = (offset_z0) + (1 - 1j) * (offset_loss / (4 * np.pi * freq.f)) * np.sqrt(freq.f / 1e9)\n",
    "    return gamma_l, zc\n",
    "```\n",
    "\n",
    "### Code Snippet\n",
    "\n",
    "Replace `keysight_calkit_offset_line()` in the code snippet above with the following\n",
    "definition to replicate the exact Keysight data:\n",
    "\n",
    "```python\n",
    "def keysight_calkit_offset_line(freq, offset_delay, offset_loss, offset_z0, ref_z0):\n",
    "    if offset_delay or offset_loss:\n",
    "        alpha_l = (offset_loss * offset_delay) / (2 * offset_z0)\n",
    "        alpha_l *= np.sqrt(freq.f / 1e9)\n",
    "        beta_l = 2 * np.pi * freq.f * offset_delay + alpha_l\n",
    "        zc = offset_z0 + (1 - 1j) * (offset_loss / (4 * np.pi * freq.f)) * np.sqrt(freq.f / 1e9)\n",
    "        gamma_l = alpha_l + beta_l * 1j\n",
    "\n",
    "        medium = DefinedGammaZ0(frequency=freq, z0=zc, gamma=gamma_l, z0_port=ref_z0)\n",
    "        offset_line = medium.line(d=1, unit='m')\n",
    "        return medium, offset_line\n",
    "    else:\n",
    "        medium = DefinedGammaZ0(frequency=freq, z0=ref_z0)\n",
    "        line = medium.line(d=0)\n",
    "        return medium, line\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "## References\n",
    "\n",
    "<div id=\"ref1\"></div>\\[1] “[Agilent Technologies 85056A 2.4 mm precision calibration kit](https://perma.cc/9M6N-PDXJ),” Agilent Technologies, User’s and Service Guide 85056–90020, Jan. 2002.\n",
    "\n",
    "<div id=\"ref2\"></div>\\[2] N. M. Ridler, “[Connectors, air lines and RF impedance](https://doi.org/10.1049/ic:20050150),” in 14th IEE Microwave Measurements Training Course, 2005, p. 9/1-9/18. \"*the test port connector’s mating mechanism affects the established measurement reference plane which limits accurate characterisation as a standard. \\[...] problem can be overcome either by depressing the mating mechanism using a dielectric plug or attaching a length of line to the centre conductor, terminated in an abrupt truncation. The dielectric plug technique is used as a standard with numerous VNA calibration kits.*\"\n",
    "\n",
    "<div id=\"ref3\"></div>\\[3] [IEEE standard for precision coaxial connectors (DC to 110 GHz)](https://doi.org/10.1109/IEEESTD.2007.4317507), IEEE 287-2007, 2007. \"*The reference plane for the Type N connector is the junction surface of the outer conductors \\[...\\] Unlike many of the other pin and socket connectors, the junction surface of the inner conductor is offset from the reference plane \\[...\\] The offset specifications in this document for the inner conductor pin differ from other common standards documents. Those documents specify the offset of the inner conductor pin as 5.283 mm through 5.360 mm (0.208 in through 0.211 in). This standard specifies the offset as 5.258 mm through 5.360 mm (0.207 in through 0.211 in).*\"\n",
    "\n",
    "<div id=\"ref4\"></div>\\[4] “[Specifying calibration standards and kits for Agilent vector network analyzers](https://web.archive.org/web/20230605182500/https://people.ece.ubc.ca/robertor/Links_files/Files/AN-1287-11.pdf),” Agilent Technologies, Application Note 1287–11, Aug. 2009. See Equation 36, 37 for the propagation constant formulas of the offset transmission line.\n",
    "\n",
    "<div id=\"ref5\"></div>\\[5] P. J. Pupalaikis, [S-parameters for Signal Integrity](https://doi.org/10.1017/9781108784863). Cambridge: Cambridge University Press, 2020. Page 481. Written by a former signal integrity expert at LeCroy, the author of this tutorial recommends everyone who simulates or measures RF/microwave devices to get a copy. Each concept is accomplished by both formulas and executable code, making it an invaluable reference in this field.\n",
    "\n",
    "<div id=\"ref6\"></div>\\[6] “[Agilent Technologies 85033D 3.5 mm calibration kit](https://web.archive.org/web/20240406061304/https://web.ece.ucsb.edu/Faculty/rodwell/Classes/ECE218a/documentation/85033D_cal_kit_info.pdf),” Agilent Technologies, User’s and Service Guide 85033–90027, July 2002. \"*The offset opens have inner conductors that are supported by a strong, low-dielectric constant plastic to minimize compensation values.*\"\n",
    "\n",
    "<div id=\"ref7\"></div>\\[7] R. B. Marks and D. F. Williams, “[A general waveguide circuit theory](https://doi.org/10.6028/jres.097.024),” Journal of research of the National Institute of Standards and Technology, vol. 97, no. 5, pp. 533–562, 1992. \"*This potentially useful result suggests that a particular line may be used as a calibration standard for any network analyzer with identical results. However, the assumption that v and i are continuous, which led to the result, is not generally valid. The example of a 50 Ω, 2.4 mm coaxial standard used on 50 Ω, 3.5 mm coaxial test ports makes this clear, for the standard must reflect the traveling waves even though its characteristic impedance is appropriate for a reflectionless standard. In general, the quality of the approximation depends in detail on the nature of the waveguide interface.*\"\n",
    "\n",
    "<div id=\"ref8\"></div>\\[8] R. D. Pollard, “[Compensation technique improves measurements for a range of mechanically compatible connectors](https://web.archive.org/web/20250807132508/https://file.elecfans.com/web1/M00/61/D5/o4YBAFuGjPGAAB2WABS0KM2eFts501.pdf),” Microwave Journal, vol. 37, no. 10, p. 91+, Oct. 1994.\n",
    "\n",
    "<div id=\"ref9\"></div>\\[9]: J. R. Juroshek, C. A. Hoer, and R. F. Kaiser, “[Calibrating network analyzers with imperfect test ports](https://doi.org/10.1109/19.31010),” IEEE Transactions on Instrumentation and Measurement, vol. 38, no. 4, pp. 898–901, Aug. 1989, doi: 10.1109/19.31010.\n",
    "\n",
    "<div id=\"ref10\"></div>\\[10]: P. I. Somlo, “[The discontinuity capacitance and the effective position of a shielded open circuit in a coaxial line](https://archive.org/details/somlo1967),” Proceedings of the Institution of Radio and Electrical Engineers Australia, vol. 28, no. 1, pp. 7–9, Jan. 1967, doi: 10.5281/zenodo.17015632, [alt link](https://zenodo.org/records/17015632).\n",
    "\n",
    "<div id=\"ref11\"></div>\\[11] N. M. Ridler and M. Salter, “[Measuring the capacitance coefficients of coaxial open-circuits with traceability to national standards](https://perma.cc/HR8U-933S),” Microwave Journal, vol. 49, pp. 138–154, Oct. 2006.\n",
    "\n",
    "<div id=\"ref12\"></div>\\[12] N. M. Ridler, J. C. Medley, A. J. B. Fuller, and M. Runham, “[Computer generated equivalent circuit models for coaxial-line offset open circuits](https://doi.org/10.1049/ip-a-3.1992.0039),” IEE Proceedings A (Science, Measurement and Technology), vol. 139, no. 5, pp. 229–231, 1992.\n",
    "\n",
    "<div id=\"ref13\"></div>\\[13] “[Specifying calibration standards for the HP 8510 network analyzer](https://web.archive.org/web/20230620204616/https://hpmemoryproject.org/an/pdf/pn8510-5.pdf),” Hewlett-Packard, Product Note 8510–5, Mar. 1986. *Note the absence of any inductance parameters in both the text and the calibration standard datasheet on the last page.*\n",
    "\n",
    "<div id=\"ref14\"></div>\\[14] “[Agilent Technologies 85052D 3.5 mm economy calibration kit](https://web.archive.org/web/20250809205839/https://www.testunlimited.com/pdf/Keysight_85052D.pdf),” Agilent Technologies, User’s and Service Guide 85052–90079, Aug. 2010.\n",
    "\n",
    "<div id=\"ref15\"></div>\\[15] M. Wollensack and J. Hoffmann, “[METAS VNA Tools - Math Reference V2.9.0.](https://web.archive.org/web/20250829054357/https://www.metas.ch/dam/metas/en/data/fachbereiche/hochfrequenz/vna-tools/vnatools_v2_9_0/vnatools_math_v2.9.0-e.pdf.download.pdf/vnatools_math_v2.9.0-e.pdf)” Federal Institute of Metrology (METAS), Aug. 08, 2025. See Page 26, 27 for formulas of the Keysight and R&S coefficients. The Keysight formulas are equivalent to\n",
    "[[4](SOLT%20Calibration%20Standards%20Creation.html#ref4)].\n",
    "\n",
    "<div id=\"ref16\"></div>\\[16] K. Wong and J. Hoffmann, “[Improving VNA measurement accuracy by including connector effects in the models of calibration standards](https://doi.org/10.1109/ARFTG-2.2013.6737334),” in 82nd ARFTG Microwave Measurement Conference, Nov. 2013, pp. 1–7.\n",
    "\n",
    "<div id=\"ref17\"></div>\\[17] J. Ruefenacht and J. Hoffmann, “[Coaxial connector effects](https://perma.cc/9YLP-83J8),” presented at the EURAMET RF&MW expert meeting, Torino, May 2011. doi: 10.13140/2.1.3666.7685.\n",
    "\n",
    "<div id=\"ref18\"></div>\\[18] P. I. Somlo, “[The computation of coaxial line step capacitances](https://doi.org/10.1109/TMTT.1967.1126368),” IEEE Transactions on Microwave Theory and Techniques, vol. 15, no. 1, pp. 48–53, Jan. 1967.\n",
    "\n",
    "<div id=\"ref19\"></div>\\[19] “[3.5 mm Coaxial Calibration Kit - DC to 26.5 GHz - Models: 8050CK10/11 - 8050CK20/21,](https://web.archive.org/web/20250906090633/https://maurymw.com/wp-content/uploads/8050-511.pdf)” Maury Microwave, User Guide 8050–511, 2015. Source of the Maury Microwave 8050CK10 example. The coefficients of this calibration kit are given in multiple formats (Anritsu, Keysight, and R&S). You can compare their differences.\n",
    "\n",
    "<div id=\"ref20\"></div>\\[20] R. Monsalve, “[Effect of loss on VNA calibration standards](https://web.archive.org/web/20201230221105/https://loco.lab.asu.edu/loco-memos/edges_reports/report_20130807.pdf),” SESE, Arizona State University, Aug. 2013.\n",
    "\n",
    "<div id=\"ref21\"></div> \\[21] D. M. Pozar, Microwave engineering, Fourth edition. Hoboken, NJ: John Wiley & Sons, Inc, 2012.\n",
    "\n",
    "<div id=\"ref22\"></div> \\[22] “[Keysight vector network analyzer calibration kit standards definitions](https://web.archive.org/web/20250828172137/https://www.keysight.com/us/en/assets/9922-01521/technical-specifications/Calibration-Kit-Definitions.pdf).” Keysight Technologies, 2024.\n",
    "\n",
    "<div id=\"ref23\"></div> \\[23] K. Poulsen, “[How to fabricate a N male and female calibration kit with arbitrary calibration data for VNWA 2 and VNWA3](https://web.archive.org/web/20250916235928/https://www.hamcom.dk/VNWA/How%20to%20fabricate%20a%20N%20Male%20and%20Female%20calibration%20kit.pdf).” Feb. 2014.\n",
    "\n",
    "<div id=\"ref24\"></div> \\[24] J. Hoffmann, M. Wollensack, J. Ruefenacht, and M. Zeier, “[Extended S-parameters for imperfect test ports](https://doi.org/10.1088/0026-1394/52/1/121),” Metrologia, vol. 52, no. 1, p. 121, Jan. 2015, doi: 10.1088/0026-1394/52/1/121.\n",
    "\n",
    "<div id=\"ref25\"></div> \\[25] J. Ruefenacht and J. Hoffmann, “[Coaxial connector effects](https://perma.cc/9YLP-83J8),” presented at the EURAMET RF&MW expert meeting, Torino, May 2011. doi: 10.13140/2.1.3666.7685.\n",
    "\n",
    "<div id=\"ref26\"></div> \\[26] K. Wong and J. Hoffmann, “[Improving VNA measurement accuracy by including connector effects in the models of calibration standards](https://doi.org/10.1109/ARFTG-2.2013.6737334),” in 82nd ARFTG Microwave Measurement Conference, Nov. 2013, pp. 1–7. doi: 10.1109/ARFTG-2.2013.6737334."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
